﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using WhittleList.Tests.Mocks;
using WhittleList.Controllers;
using System.Web.Mvc;
using WhittleList.Bll;

namespace WhittleList.Tests.Controllers
{
    [TestClass]
    public class ItemControllerTest
    {
        private List<FakeSite> siteData;
        private List<FakeProject> projectData;
        private List<FakeSection> sectionData;
        private List<FakeItem> itemData;

        private FakeUserRepository userRepository;
        private FakeSiteRepository siteRepository;
        private FakeHttpContext httpContext;
        
        private ItemController controller;
        private Random rnd;

        [TestInitialize]
        public void Init()
        {
            rnd = new Random();
            siteData = FakeSite.GetList(2);
            projectData = FakeProject.GetList(10, rnd);
            sectionData = FakeSection.GetList(15, rnd);
            itemData = FakeItem.GetList(15, rnd);

            siteData[0].SetProjects(projectData.Take(8).ToList());
            siteData[1].SetProjects(projectData.Skip(8).ToList());

            projectData[0].SetSections(sectionData.Take(7).ToList());
            projectData[1].SetSections(sectionData.Skip(7).Take(5).ToList());
            projectData[2].SetSections(sectionData.Skip(7).Skip(5).ToList());

            sectionData[0].SetItems(itemData.Take(7).ToList());
            sectionData[1].SetItems(itemData.Skip(7).Take(5).ToList());
            sectionData[2].SetItems(itemData.Skip(7).Skip(5).ToList());

            siteRepository = new FakeSiteRepository();
            siteRepository.SetSites(siteData);
            siteRepository.SetProjects(projectData);
            siteRepository.SetSections(sectionData);
            siteRepository.SetItems(itemData);

            userRepository = new FakeUserRepository();
            userRepository.Setup(x => x.GetByEmailAddress("test")).Returns(new FakeUser(1, "Test", "User", "test", "password").Object);

            httpContext = new FakeHttpContext();
            httpContext.Request.Setup(x => x.IsAuthenticated).Returns(true);
            httpContext.Identity.Setup(x => x.IsAuthenticated).Returns(true);
            httpContext.Identity.Setup(x => x.Name).Returns("test");

            controller = new ItemController(userRepository.Object, siteRepository.Object);
            httpContext.AttachToControllerContext(controller);
        }

        [TestMethod]
        public void Index_Returns_ViewWithCorrectData()
        {
            var result = controller.Index(siteData[0].Object, sectionData[1].Object.Id);

            Assert.IsNotNull(result);
            Assert.IsInstanceOfType(result, typeof(ViewResult));

            var viewResult = (ViewResult)result;
            Assert.IsNotNull(viewResult.ViewData.Model);
            Assert.IsInstanceOfType(viewResult.ViewData.Model, typeof(List<Item>));

            var items = (List<Item>)viewResult.ViewData.Model;

            Assert.AreEqual(sectionData[1].Object.Items.Count, items.Count);
        }


        [TestMethod]
        public void Details_Returns_ViewWithCorrectData()
        {
            var result = controller.Details(siteData[0].Object, itemData[1].Object.Id);

            Assert.IsNotNull(result);
            Assert.IsInstanceOfType(result, typeof(ViewResult));

            var viewResult = (ViewResult)result;
            Assert.IsNotNull(viewResult.ViewData.Model);
            Assert.IsInstanceOfType(viewResult.ViewData.Model, typeof(Item));

            var item = (Item)viewResult.ViewData.Model;

            Assert.AreEqual(itemData[1].Object, item);
        }

        [TestMethod]
        public void Create_Returns_ViewWithCorrectData()
        {
            var result = controller.Create(siteData[0].Object, sectionData[3].Object.Id);

            Assert.IsNotNull(result);
            Assert.IsInstanceOfType(result, typeof(ViewResult));

            var viewResult = (ViewResult)result;
            Assert.IsNull(viewResult.ViewData.Model);

        }

        [TestMethod]
        public void Edit_Returns_ViewWithCorrectData()
        {
            var result = controller.Edit(siteData[0].Object, itemData[3].Object.Id);

            Assert.IsNotNull(result);
            Assert.IsInstanceOfType(result, typeof(ViewResult));

            var viewResult = (ViewResult)result;

            Assert.IsNotNull(viewResult.ViewData.Model);
            Assert.IsInstanceOfType(viewResult.ViewData.Model, typeof(Item));

            var item = (Item)viewResult.ViewData.Model;

            Assert.AreEqual(itemData[3].Object, item);
        }

        [TestMethod]
        public void Edit_Post_CorrectData_Returns_RedirectToAction()
        {
            var result = controller.Edit(siteData[0].Object, itemData[3].Object.Id, "New Name", "Notes Updated", 2, itemData[3].Object.CID);

            Assert.IsNotNull(result);
            Assert.IsInstanceOfType(result, typeof(RedirectToRouteResult));

            var viewResult = (RedirectToRouteResult)result;

            Assert.IsNotNull(viewResult.RouteValues["Action"]);
            Assert.AreEqual("Index", viewResult.RouteValues["Action"]);

            Assert.AreEqual("New Name", itemData[3].Object.Name);
            Assert.AreEqual("Notes Updated", itemData[3].Object.Notes);
            Assert.AreEqual(2, itemData[3].Object.Importance);
            siteRepository.Verify(x => x.SaveChanges(Moq.It.IsAny<User>()), Moq.Times.Once());
        }

        [TestMethod]
        public void Create_Post_CorrectData_Returns_RedirectToAction()
        {
            Item created = null;
            
            siteRepository.Setup(x => x.Insert(Moq.It.IsAny<Item>())).Callback<Item>(x => created = x);

            var result = controller.Create(siteData[0].Object, sectionData[3].Object.Id, "New Name", "New Notes", 2);

            Assert.IsNotNull(result);
            Assert.IsInstanceOfType(result, typeof(RedirectToRouteResult));

            var viewResult = (RedirectToRouteResult)result;

            Assert.IsNotNull(viewResult.RouteValues["Action"]);
            Assert.AreEqual("Index", viewResult.RouteValues["Action"]);

            Assert.IsNotNull(created);
            Assert.AreEqual("New Name", created.Name);
            Assert.AreEqual("New Notes", created.Notes);
            Assert.AreEqual(2, created.Importance);

            siteRepository.Verify(x => x.Insert(Moq.It.IsAny<Item>()), Moq.Times.Once());
            siteRepository.Verify(x => x.SaveChanges(Moq.It.IsAny<User>()), Moq.Times.Once());
        }

        [TestMethod]
        public void Edit_Post_DuplicateNameException_ReturnsCorrectView()
        {
            var site = siteData[0].Object;
            var item = itemData[3].Object;
            siteRepository.Setup(x => x.SaveChanges(Moq.It.IsAny<User>())).Throws(new WhittleList.Bll.Exceptions.DuplicateNameException());

            var result = controller.Edit(site, item.Id, "New Name", "New Notes", 2, itemData[3].Object.CID);

            Assert.IsNotNull(result);
            Assert.IsInstanceOfType(result, typeof(ViewResult));

            var viewResult = (ViewResult)result;

            Assert.IsNotNull(viewResult.ViewData.Model);
            Assert.IsInstanceOfType(viewResult.ViewData.Model, typeof(Item));

            Assert.AreEqual(item, viewResult.ViewData.Model);

            Assert.IsFalse(viewResult.ViewData.ModelState.IsValid);
            Assert.IsNotNull(viewResult.ViewData.ModelState["Name"]);
        }

        [TestMethod]
        public void Edit_Post_ValidationExceptions_ReturnsCorrectView()
        {
            var site = siteData[0].Object;
            var item = itemData[3].Object;

            var errors = new RuleViolation[] { new RuleViolation("Name", "Name Exception"), new RuleViolation("OtherField", "Other Field Exception") };
            siteRepository.Setup(x => x.SaveChanges(Moq.It.IsAny<User>())).Throws(new WhittleList.Bll.Exceptions.ValidationException("VAlidation Error prevented saving", errors));

            var result = controller.Edit(site, item.Id, "New Name", "New Notes", 2, itemData[3].Object.CID);

            Assert.IsNotNull(result);
            Assert.IsInstanceOfType(result, typeof(ViewResult));

            var viewResult = (ViewResult)result;

            Assert.IsNotNull(viewResult.ViewData.Model);
            Assert.IsInstanceOfType(viewResult.ViewData.Model, typeof(Item));

            Assert.AreEqual(item, viewResult.ViewData.Model);

            Assert.IsFalse(viewResult.ViewData.ModelState.IsValid);
            Assert.IsNotNull(viewResult.ViewData.ModelState["Name"]);
            Assert.IsNotNull(viewResult.ViewData.ModelState["OtherField"]);
        }

        [TestMethod]
        public void Edit_Post_ConcurencyException_ReturnsCorrectView()
        {
            var site = siteData[0].Object;
            var item = itemData[3].Object;

            var errors = new RuleViolation[] { new RuleViolation("Name", "Name Exception"), new RuleViolation("OtherField", "Other Field Exception") };
            var newCid = Helpers.MockHelpers.CreateBinary(rnd);

            var result = controller.Edit(site, item.Id, "New Name", "New Notes", 2, newCid);

            Assert.IsNotNull(result);
            Assert.IsInstanceOfType(result, typeof(ViewResult));

            var viewResult = (ViewResult)result;

            Assert.IsNotNull(viewResult.ViewData.Model);
            Assert.IsInstanceOfType(viewResult.ViewData.Model, typeof(Item));

            Assert.AreEqual(item, viewResult.ViewData.Model);

            Assert.IsFalse(viewResult.ViewData.ModelState.IsValid);
            Assert.AreEqual(1, viewResult.ViewData.ModelState.Count);
            Assert.IsNotNull(viewResult.ViewData.ModelState["_Form"]);
        }

        [TestMethod]
        [ExpectedException(typeof(NotImplementedException))]
        public void Index_NullSite_ThrowsNotImplimentedException()
        {
            controller.Index(null, sectionData[3].Object.Id);
        }

        [TestMethod]
        [ExpectedException(typeof(NotImplementedException))]
        public void Details_NullSite_ThrowsNotImplimentedException()
        {
            controller.Details(null, sectionData[0].Object.Items[0].Id);
        }

        [TestMethod]
        [ExpectedException(typeof(NotImplementedException))]
        public void Edit_NullSite_ThrowsNotImplimentedException()
        {
            controller.Edit(null, sectionData[0].Object.Items[0].Id);
        }

        [TestMethod]
        [ExpectedException(typeof(NotImplementedException))]
        public void Edit_Post_NullSite_ThrowsNotImplimentedException()
        {
            controller.Edit(null, sectionData[0].Object.Items[0].Id, "name", "notes", 0, sectionData[0].Object.Items[0].CID);
        }

        [TestMethod]
        [ExpectedException(typeof(NotImplementedException))]
        public void Create_NullSite_ThrowsNotImplimentedException()
        {
            controller.Create(null, sectionData[3].Object.Id);
        }

        [TestMethod]
        [ExpectedException(typeof(NotImplementedException))]
        public void Create_Post_NullSite_ThrowsNotImplimentedException()
        {
            controller.Create(null, sectionData[3].Object.Id, "name", "notes", 0);
        }

        [TestMethod]
        public void Create_Post_DuplicateNameException_ReturnsCorrectView()
        {
            var site = siteData[0].Object;
            var section = sectionData[3].Object;
            siteRepository.Setup(x => x.SaveChanges(Moq.It.IsAny<User>())).Throws(new WhittleList.Bll.Exceptions.DuplicateNameException());

            var result = controller.Create(site, section.Id, "New Name", "New Notes", 2);

            Assert.IsNotNull(result);
            Assert.IsInstanceOfType(result, typeof(ViewResult));

            var viewResult = (ViewResult)result;

            Assert.IsNotNull(viewResult.ViewData.Model);
            Assert.IsInstanceOfType(viewResult.ViewData.Model, typeof(Item));

            var newItem = (Item)viewResult.ViewData.Model;
            Assert.AreEqual("New Name", newItem.Name);
            Assert.AreEqual("New Notes", newItem.Notes);
            Assert.AreEqual(2, newItem.Importance);

            Assert.IsFalse(viewResult.ViewData.ModelState.IsValid);
            Assert.IsNotNull(viewResult.ViewData.ModelState["Name"]);
        }

        [TestMethod]
        public void Create_Post_ValidationExceptions_ReturnsCorrectView()
        {
            var site = siteData[0].Object;
            var section = sectionData[2].Object;

            var errors = new RuleViolation[] { new RuleViolation("Name", "Name Exception"), new RuleViolation("OtherField", "Other Field Exception") };
            siteRepository.Setup(x => x.SaveChanges(Moq.It.IsAny<User>())).Throws(new WhittleList.Bll.Exceptions.ValidationException("VAlidation Error prevented saving", errors));

            var result = controller.Create(site, section.Id, "New Name", "New Notes", 2);

            Assert.IsNotNull(result);
            Assert.IsInstanceOfType(result, typeof(ViewResult));

            var viewResult = (ViewResult)result;

            Assert.IsNotNull(viewResult.ViewData.Model);
            Assert.IsInstanceOfType(viewResult.ViewData.Model, typeof(Item));

            var newItem = (Item)viewResult.ViewData.Model;
            Assert.AreEqual("New Name", newItem.Name);
            Assert.AreEqual("New Notes", newItem.Notes);
            Assert.AreEqual(2, newItem.Importance);

            Assert.IsFalse(viewResult.ViewData.ModelState.IsValid);
            Assert.IsNotNull(viewResult.ViewData.ModelState["Name"]);
            Assert.IsNotNull(viewResult.ViewData.ModelState["OtherField"]);
        }
    }
}
